package com.qumitech.visualnovel.component.concurrent;

import java.util.AbstractList;
import java.util.Collection;
import java.util.NoSuchElementException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/* ------------------------------------------------------------ */

/**
 * 其实是jetty源码jetty util里的
 * 
 * @author smartlv
 * @param <E>
 */
public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQueue<E>
{
    public final int DEFAULT_CAPACITY = 128;
    public final int DEFAULT_GROWTH = 64;
    private final int _limit;
    private final AtomicInteger _size = new AtomicInteger();
    private final int _growCapacity;

    private volatile int _capacity;
    private Object[] _elements;
    private int _head;
    private int _tail;

    private final ReentrantLock _headLock = new ReentrantLock();
    private final Condition _notEmpty = _headLock.newCondition();
    private final ReentrantLock _tailLock = new ReentrantLock();

    /* ------------------------------------------------------------ */
    /**
     * Create a growing partially blocking Queue
     */
    public BlockingArrayQueue()
    {
        _elements = new Object[DEFAULT_CAPACITY];
        _growCapacity = DEFAULT_GROWTH;
        _capacity = _elements.length;
        _limit = Integer.MAX_VALUE;
    }

    /* ------------------------------------------------------------ */
    /**
     * Create a fixed size partially blocking Queue
     * 
     * @param limit
     *        The initial capacity and the limit.
     */
    public BlockingArrayQueue(int limit)
    {
        _elements = new Object[limit];
        _capacity = _elements.length;
        _growCapacity = -1;
        _limit = limit;
    }

    /* ------------------------------------------------------------ */
    /**
     * Create a growing partially blocking Queue.
     * 
     * @param capacity
     *        Initial capacity
     * @param growBy
     *        Incremental capacity.
     */
    public BlockingArrayQueue(int capacity, int growBy)
    {
        _elements = new Object[capacity];
        _capacity = _elements.length;
        _growCapacity = growBy;
        _limit = Integer.MAX_VALUE;
    }

    /* ------------------------------------------------------------ */
    /**
     * Create a growing limited partially blocking Queue.
     * 
     * @param capacity
     *        Initial capacity
     * @param growBy
     *        Incremental capacity.
     * @param limit
     *        maximum capacity.
     */
    public BlockingArrayQueue(int capacity, int growBy, int limit)
    {
        if (capacity > limit)
            throw new IllegalArgumentException();

        _elements = new Object[capacity];
        _capacity = _elements.length;
        _growCapacity = growBy;
        _limit = limit;
    }

    /* ------------------------------------------------------------ */
    public int getCapacity()
    {
        return _capacity;
    }

    /* ------------------------------------------------------------ */
    public int getLimit()
    {
        return _limit;
    }

    /* ------------------------------------------------------------ */
    @Override
    public boolean add(E e)
    {
        return offer(e);
    }

    /* ------------------------------------------------------------ */
    @Override
    public E element()
    {
        E e = peek();
        if (e == null)
            throw new NoSuchElementException();
        return e;
    }

    /* ------------------------------------------------------------ */
    @Override
    public E peek()
    {
        if (_size.get() == 0)
            return null;

        E e = null;
        _headLock.lock(); // Size cannot shrink
        try
        {
            if (_size.get() > 0)
                e = (E) _elements[_head];
        }
        finally
        {
            _headLock.unlock();
        }

        return e;
    }

    /* ------------------------------------------------------------ */
    @Override
    public boolean offer(E e)
    {
        if (e == null)
            throw new NullPointerException();

        boolean not_empty = false;
        _tailLock.lock(); // size cannot grow... only shrink
        try
        {
            if (_size.get() >= _limit)
                return false;

            // should we expand array?
            if (_size.get() == _capacity)
            {
                _headLock.lock(); // Need to grow array
                try
                {
                    if (!grow())
                        return false;
                }
                finally
                {
                    _headLock.unlock();
                }
            }

            // add the element
            _elements[_tail] = e;
            _tail = (_tail + 1) % _capacity;

            not_empty = 0 == _size.getAndIncrement();

        }
        finally
        {
            _tailLock.unlock();
        }

        if (not_empty)
        {
            _headLock.lock();
            try
            {
                _notEmpty.signal();
            }
            finally
            {
                _headLock.unlock();
            }
        }

        return true;
    }

    /* ------------------------------------------------------------ */
    @Override
    public E poll()
    {
        if (_size.get() == 0)
            return null;

        E e = null;
        _headLock.lock(); // Size cannot shrink
        try
        {
            if (_size.get() > 0)
            {
                final int head = _head;
                e = (E) _elements[head];
                _elements[head] = null;
                _head = (head + 1) % _capacity;

                if (_size.decrementAndGet() > 0)
                    _notEmpty.signal();
            }
        }
        finally
        {
            _headLock.unlock();
        }

        return e;
    }

    /* ------------------------------------------------------------ */
    /**
     * Retrieves and removes the head of this queue, waiting if no elements are present on this queue.
     * 
     * @return the head of this queue
     * @throws InterruptedException
     *         if interrupted while waiting.
     */
    @Override
    public E take() throws InterruptedException
    {
        E e = null;
        _headLock.lockInterruptibly(); // Size cannot shrink
        try
        {
            try
            {
                while (_size.get() == 0)
                {
                    _notEmpty.await();
                }
            }
            catch (InterruptedException ie)
            {
                _notEmpty.signal();
                throw ie;
            }

            final int head = _head;
            e = (E) _elements[head];
            _elements[head] = null;
            _head = (head + 1) % _capacity;

            if (_size.decrementAndGet() > 0)
                _notEmpty.signal();
        }
        finally
        {
            _headLock.unlock();
        }

        return e;
    }

    /* ------------------------------------------------------------ */
    /**
     * Retrieves and removes the head of this queue, waiting if necessary up to the specified wait time if no elements
     * are present on this queue.
     * 
     * @param time
     *        how long to wait before giving up, in units of <tt>unit</tt>
     * @param unit
     *        a <tt>TimeUnit</tt> determining how to interpret the <tt>timeout</tt> parameter
     * @return the head of this queue, or <tt>null</tt> if the specified waiting time elapses before an element is
     *         present.
     * @throws InterruptedException
     *         if interrupted while waiting.
     */
    @Override
    public E poll(long time, TimeUnit unit) throws InterruptedException
    {

        E e = null;

        long nanos = unit.toNanos(time);

        _headLock.lockInterruptibly(); // Size cannot shrink
        try
        {
            try
            {
                while (_size.get() == 0)
                {
                    if (nanos <= 0)
                        return null;
                    nanos = _notEmpty.awaitNanos(nanos);
                }
            }
            catch (InterruptedException ie)
            {
                _notEmpty.signal();
                throw ie;
            }

            e = (E) _elements[_head];
            _elements[_head] = null;
            _head = (_head + 1) % _capacity;

            if (_size.decrementAndGet() > 0)
                _notEmpty.signal();
        }
        finally
        {
            _headLock.unlock();
        }

        return e;
    }

    /* ------------------------------------------------------------ */
    @Override
    public E remove()
    {
        E e = poll();
        if (e == null)
            throw new NoSuchElementException();
        return e;
    }

    /* ------------------------------------------------------------ */
    @Override
    public void clear()
    {
        _tailLock.lock();
        try
        {
            _headLock.lock();
            try
            {
                _head = 0;
                _tail = 0;
                _size.set(0);
            }
            finally
            {
                _headLock.unlock();
            }
        }
        finally
        {
            _tailLock.unlock();
        }
    }

    /* ------------------------------------------------------------ */
    @Override
    public boolean isEmpty()
    {
        return _size.get() == 0;
    }

    /* ------------------------------------------------------------ */
    @Override
    public int size()
    {
        return _size.get();
    }

    /* ------------------------------------------------------------ */
    @Override
    public E get(int index)
    {
        _tailLock.lock();
        try
        {
            _headLock.lock();
            try
            {
                if (index < 0 || index >= _size.get())
                    throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");
                int i = _head + index;
                if (i >= _capacity)
                    i -= _capacity;
                return (E) _elements[i];
            }
            finally
            {
                _headLock.unlock();
            }
        }
        finally
        {
            _tailLock.unlock();
        }
    }

    /* ------------------------------------------------------------ */
    @Override
    public E remove(int index)
    {
        _tailLock.lock();
        try
        {
            _headLock.lock();
            try
            {

                if (index < 0 || index >= _size.get())
                    throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");

                int i = _head + index;
                if (i >= _capacity)
                    i -= _capacity;
                E old = (E) _elements[i];

                if (i < _tail)
                {
                    System.arraycopy(_elements, i + 1, _elements, i, _tail - i);
                    _tail--;
                    _size.decrementAndGet();
                }
                else
                {
                    System.arraycopy(_elements, i + 1, _elements, i, _capacity - i - 1);
                    if (_tail > 0)
                    {
                        _elements[_capacity] = _elements[0];
                        System.arraycopy(_elements, 1, _elements, 0, _tail - 1);
                        _tail--;
                    }
                    else
                        _tail = _capacity - 1;

                    _size.decrementAndGet();
                }

                return old;
            }
            finally
            {
                _headLock.unlock();
            }
        }
        finally
        {
            _tailLock.unlock();
        }
    }

    /* ------------------------------------------------------------ */
    @Override
    public E set(int index, E e)
    {
        if (e == null)
            throw new NullPointerException();

        _tailLock.lock();
        try
        {
            _headLock.lock();
            try
            {

                if (index < 0 || index >= _size.get())
                    throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");

                int i = _head + index;
                if (i >= _capacity)
                    i -= _capacity;
                E old = (E) _elements[i];
                _elements[i] = e;
                return old;
            }
            finally
            {
                _headLock.unlock();
            }
        }
        finally
        {
            _tailLock.unlock();
        }
    }

    /* ------------------------------------------------------------ */
    @Override
    public void add(int index, E e)
    {
        if (e == null)
            throw new NullPointerException();

        _tailLock.lock();
        try
        {
            _headLock.lock();
            try
            {

                if (index < 0 || index > _size.get())
                    throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");

                if (index == _size.get())
                {
                    add(e);
                }
                else
                {
                    if (_tail == _head)
                        if (!grow())
                            throw new IllegalStateException("full");

                    int i = _head + index;
                    if (i >= _capacity)
                        i -= _capacity;

                    _size.incrementAndGet();
                    _tail = (_tail + 1) % _capacity;

                    if (i < _tail)
                    {
                        System.arraycopy(_elements, i, _elements, i + 1, _tail - i);
                        _elements[i] = e;
                    }
                    else
                    {
                        if (_tail > 0)
                        {
                            System.arraycopy(_elements, 0, _elements, 1, _tail);
                            _elements[0] = _elements[_capacity - 1];
                        }

                        System.arraycopy(_elements, i, _elements, i + 1, _capacity - i - 1);
                        _elements[i] = e;
                    }
                }
            }
            finally
            {
                _headLock.unlock();
            }
        }
        finally
        {
            _tailLock.unlock();
        }
    }

    /* ------------------------------------------------------------ */
    private boolean grow()
    {
        if (_growCapacity <= 0)
            return false;

        _tailLock.lock();
        try
        {
            _headLock.lock();
            try
            {
                final int head = _head;
                final int tail = _tail;
                final int new_tail;

                Object[] elements = new Object[_capacity + _growCapacity];

                if (head < tail)
                {
                    new_tail = tail - head;
                    System.arraycopy(_elements, head, elements, 0, new_tail);
                }
                else if (head > tail || _size.get() > 0)
                {
                    new_tail = _capacity + tail - head;
                    int cut = _capacity - head;
                    System.arraycopy(_elements, head, elements, 0, cut);
                    System.arraycopy(_elements, 0, elements, cut, tail);
                }
                else
                {
                    new_tail = 0;
                }

                _elements = elements;
                _capacity = _elements.length;
                _head = 0;
                _tail = new_tail;
                return true;
            }
            finally
            {
                _headLock.unlock();
            }
        }
        finally
        {
            _tailLock.unlock();
        }

    }

    /* ------------------------------------------------------------ */
    @Override
    public int drainTo(Collection<? super E> c)
    {
        throw new UnsupportedOperationException();
    }

    /* ------------------------------------------------------------ */
    @Override
    public int drainTo(Collection<? super E> c, int maxElements)
    {
        throw new UnsupportedOperationException();
    }

    /* ------------------------------------------------------------ */
    @Override
    public boolean offer(E o, long timeout, TimeUnit unit) throws InterruptedException
    {
        throw new UnsupportedOperationException();
    }

    /* ------------------------------------------------------------ */
    @Override
    public void put(E o) throws InterruptedException
    {
        if (!add(o))
            throw new IllegalStateException("full");
    }

    /* ------------------------------------------------------------ */
    @Override
    public int remainingCapacity()
    {
        _tailLock.lock();
        try
        {
            _headLock.lock();
            try
            {
                return getCapacity() - size();
            }
            finally
            {
                _headLock.unlock();
            }
        }
        finally
        {
            _tailLock.unlock();
        }
    }
}
